package gerror

import (
	"fmt"
	"io"
	"runtime"
	"strconv"
	"strings"
)

type Caller []uintptr // 获取的堆栈指针切片(注意: 序列化时指针无效做丢弃处理)
type Source struct {
	File string
	Func string
	Line int
}

func stackFuncName(name string) string {
	i := strings.LastIndex(name, "/")
	name = name[i+1:]
	i = strings.Index(name, ".")
	return name[i+1:]
}

// Source 获取Caller堆栈Source信息(第一条堆栈信息)
//   - source 返回值: 获取成功时返回Source对象
//   - exists 返回值: 未开启堆栈或获取失败时返回 false
func (c *Caller) Source() (source Source, exists bool) {
	if c == nil || len(*c) == 0 {
		return
	}
	if frames := runtime.CallersFrames([]uintptr{(*c)[0]}); frames != nil {
		frame, _ := frames.Next()
		if frame.Function != "" {
			return Source{
				File: frame.File,
				Line: frame.Line,
				Func: stackFuncName(frame.Function),
			}, true
		}
	}
	return
}

// Stacks 获取Caller堆栈的[]Source信息切片
//   - 返回值: 未开启堆栈或获取失败时返回 nil
func (c *Caller) Stacks() []Source {
	if c == nil || len(*c) == 0 {
		return nil
	}
	// 解析并缓存堆栈信息
	stacks := []Source{}
	j := 0
	if frames := runtime.CallersFrames(*c); frames != nil {
		for {
			j++
			frame, more := frames.Next()
			stacks = append(stacks, Source{
				File: frame.File,
				Line: frame.Line,
				Func: stackFuncName(frame.Function),
			})
			if !more {
				break
			}
		}
	}
	return stacks
}

// Format Source formats the frame according to the fmt.Formatter interface.
func (c *Caller) Format(s fmt.State, verb rune) {
	if c == nil {
		return
	}
	if stacks := c.Stacks(); stacks != nil {
		for _, f := range stacks {
			io.WriteString(s, "\n")
			f.Format(s, verb)
		}
	}
}

// Format Source formats the frame according to the fmt.Formatter interface.
//
//	%s    source file
//	%d    source line
//	%n    function name
//	%v    equivalent to %s:%d
//
// Format accepts flags that alter the printing of some verbs, as follows:
//
//	%+s   function name and path of source file relative to the compile time
//	      GOPATH separated by \n\t (<funcname>\n\t<path>)
//	%+v   equivalent to %+s:%d
func (sc Source) Format(s fmt.State, verb rune) {
	switch verb {
	case 's':
		switch {
		case s.Flag('+'):
			io.WriteString(s, sc.Func)
			io.WriteString(s, "\n\t")
			io.WriteString(s, sc.File)
		default:
			io.WriteString(s, sc.File)
		}
	case 'd':
		io.WriteString(s, strconv.Itoa(sc.Line))
	case 'n':
		io.WriteString(s, sc.Func)
	case 'v':
		sc.Format(s, 's')
		io.WriteString(s, ":")
		sc.Format(s, 'd')
	}
}

// newCaller 获取 runtime.Callers 调用栈指针, 用于解析文件名、行号、函数名
//   - skips: 跳过栈帧数, 默认为0 => 表示外部调用 newCaller() 的代码所在文件行
//   - 获取失败时返回 nil
//
//go:noinline
func newCaller(skips ...int) *Caller {
	var skip = 0
	if len(skips) > 0 && skips[0] >= -2 {
		skip = skips[0]
	}
	pcs := make([]uintptr, defaultStackDepth)
	n := runtime.Callers(skip+2, pcs)
	if n == 0 {
		return nil
	}
	caller := Caller(pcs[0:n])
	return &caller
}
